package com.aceitunaproject.server.map;

import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.ManagedReference;
import com.aceitunaproject.server.AceitunaContext;
import com.aceitunaproject.server.ServerConfiguration;
import com.aceitunaproject.server.entity.installation.Installation;
import com.aceitunaproject.server.entity.settlement.Settlement;
import com.aceitunaproject.server.entity.unit.Unit;
import com.aceitunaproject.server.equipment.Equipment;
import com.aceitunaproject.server.installation.InstallationManager;
import com.aceitunaproject.server.terrain.TerrainManager;
import com.aceitunaproject.server.terrain.TerrainType;

/**
 * @author Federico Bergstein
 * 
 *         represents a server side map tile in the game world
 */
public class MapTile implements ManagedObject, Serializable {

	/**
	 * Needed to implement serializable
	 */
	private static final long serialVersionUID = 1852682364601320448L;

	/**
	 * List of the unit IDs of the units present in this tile
	 */
	private LinkedList<Long> unitIDs = new LinkedList<Long>();

	/**
	 * List of the installations IDs of the installations present in this tile
	 */
	private LinkedList<Long> installationIDs = new LinkedList<Long>();

	/**
	 * List of the Terrain Type IDs present in this tile;
	 */
	private List<String> terrainTypeIDs = new LinkedList<String>();
	/**
	 * The x and y coordinates
	 */
	private int x;
	private int y;

	private ManagedReference<Settlement> settlementRef;

	/**
	 * 
	 * @return the terrain type collection of the terrain types present in this
	 *         MapTile.
	 */
	public Map<String, TerrainType> getTerrainTypes() {
		return TerrainManager.getTerrainTypeCollection(terrainTypeIDs);

	}

	public void AddTerrainType(TerrainType terrainType) {
		
		//TODO: fix the getterrainttypes test (so it uses the testing framework) then uncomment.
		//AppContext.getDataManager().markForUpdate(this);

		terrainTypeIDs.add(terrainType.getDefaultName());
	}

	public void RemoveTerrainType(TerrainType terrainType) {
		AppContext.getDataManager().markForUpdate(this);
		terrainTypeIDs.remove(terrainType.getDefaultName());
	}

	/**
	 * 
	 * @return a List of the unit IDs of the units present in this tile
	 */
	public LinkedList<Long> getUnitIDs() {
		return unitIDs;
	}

	/**
	 * Adds a unit to the units present in this tile
	 * 
	 * @param unitID
	 *            the unit id of the Unit to add
	 */
	public void addUnit(long unitID) {
		AppContext.getDataManager().markForUpdate(this);
		unitIDs.add(unitID);

	}

	/**
	 * Removes a unit from the units present in this tile, if the unit is not
	 * present it does nothing.
	 * 
	 * @param unitID
	 *            unit id of the Unit to remove
	 */
	public void removeUnit(long unitID) {
		AppContext.getDataManager().markForUpdate(this);
		unitIDs.remove(unitID);
	}

	/**
	 * } Checks whether a unit is present in the tile.
	 * 
	 * @param unitID
	 *            id of the unit to check for.
	 * @return whether or not the unit is there.
	 */
	public boolean unitIsInTile(long unitID) {
		return unitIDs.contains(Long.valueOf(unitID));
	}

	/**
	 * Public constructor for the class
	 * 
	 * @param x
	 *            the x coordinate position of the map tile
	 * @param y
	 *            the y coordinate position of the map tile
	 */
	public MapTile(int x, int y) {
		this.x = x;
		this.y = y;
	}

	/**
	 * Returns the tile's x coordinate
	 * 
	 * @return the tile's x coordinate
	 */
	public int getXPos() {
		return this.x;
	}

	/**
	 * Returns the tile's y coordinate
	 * 
	 * @return the tile's y coordinate
	 */
	public int getYPos() {
		return this.y;
	}

	public int getMovementCost(Unit unit) {
		// TODO account for terrain types, equipment etc
		return ServerConfiguration.getBasicMovementCost();
	}

	public boolean unitCanOccupyTile(Unit unit) {
		// TODO account for terrain types, equipment etc
		return true;
	}

	/**
	 * Places a settlement in this map tile.
	 * 
	 * @param settlement
	 *            the settlement
	 */
	public void placeSettlement(Settlement settlement) {
		AppContext.getDataManager().markForUpdate(this);
		settlementRef = AppContext.getDataManager().createReference(settlement);
		Logger.getLogger(this.getClass().getName()).info(
				"MapTile placing settlement " + settlement.getName() + " in "
						+ this.x + " , " + this.y);
	}

	public Settlement getSettlement() {

		Settlement settlement = null;
		if (settlementRef != null) {

			settlement = settlementRef.get();

		}
		return settlement;
	}

	public boolean unitCanBuildSettlement(Unit unit) {

		for (TerrainType type : this.getTerrainTypes().values()) {

			if (!type.allowsSettlements()) {
				return false;
			}

			List<Equipment> needed = type.getSettlementEquipmentNeeded();
			if (needed != null && needed.size() > 0) {
				if (!unit.getEquipment().values().containsAll(needed)) {
					return false;
				}
			}
		}

		return true;
	}

	public List<Installation>  getInstallations() {
		
		InstallationManager installationManager = AceitunaContext.getInstallationManager();
		List<Installation> retList = new LinkedList<Installation>();
		for (Long id : installationIDs) {
			retList.add(installationManager.getInstallation(id));
			
		}
		return retList;
	}

	public void addInstallation(Installation newInstallation) {
		AppContext.getDataManager().markForUpdate(this);
		installationIDs.add(newInstallation.getId());

	}
}
